# -*- coding: utf-8 -*-
"""
报表管理API
提供报表模板、配置、生成、历史等功能的RESTful接口
"""
from flask import Blueprint, request, jsonify, send_file, current_app
import json
import pymysql.cursors
from datetime import datetime, timedelta

from app.services.report_service import (
    ReportOrchestrator,
    ReportTemplateService,
    ReportConfigService,
    ReportRunService,
)
from app.blueprints.reports.providers import get_provider
from app.blueprints.reports.renderers import get_renderer
from app.services.notification_service import (
    NotificationService, ReportNotificationConfigService
)
from app.services.report_scheduler import get_report_scheduler
from app.services.data_source_service import DataSourceService
from app.utils.mysql_db import get_db_connection, is_database_connection_error
from app.utils.logger import log_api_call
from apscheduler.triggers.cron import CronTrigger
from datetime import timezone
from app.blueprints.reports.dto import (
    build_config_payload,
    build_notification_payload,
    build_run_payload,
    build_template_payload,
)

reports_bp = Blueprint('reports', __name__, url_prefix='/api/reports')


# ==================== 数据源管理 ====================

@reports_bp.route('/service-configs', methods=['GET'])
@log_api_call("reports.get_service_configs")
def get_service_configs():
    """获取Kafka/StarRocks集群配置列表（从监控表）"""
    try:
        service_type = request.args.get('service_type')  # kafka或starrocks
        
        if not service_type or service_type not in ['kafka', 'starrocks']:
            return jsonify({
                'success': False,
                'message': '参数错误：service_type必须是kafka或starrocks',
                'data': []
            }), 400
        
        # 根据service_type选择对应的监控表
        if service_type == 'kafka':
            monitor_table = 'kafka_topic_monitor'
        else:  # starrocks
            monitor_table = 'starrocks_tables'
        
        # 从监控表查询有数据的config_id，并关联service_config获取集群信息
        conn = get_db_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        
        try:
            sql = f"""
                SELECT DISTINCT 
                    sc.id, 
                    sc.service_type, 
                    sc.config_name, 
                    sc.host, 
                    sc.port, 
                    sc.description
                FROM {monitor_table} m
                INNER JOIN service_config sc ON m.config_id = sc.id
                WHERE sc.service_type = %s AND sc.is_active = 1
                ORDER BY sc.config_name
            """
            cursor.execute(sql, (service_type,))
            configs = cursor.fetchall()
            
            # 格式化返回数据
            result = []
            for config in configs:
                result.append({
                    'id': config['id'],
                    'service_type': config['service_type'],
                    'config_name': config['config_name'],
                    'host': config['host'],
                    'port': config['port'],
                    'description': config.get('description', '')
                })
            
            return jsonify({
                'success': True,
                'message': 'ok',
                'data': result
            })
        finally:
            cursor.close()
            conn.close()
            
    except Exception as e:
        current_app.logger.error(f"获取集群配置列表失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'获取集群配置列表失败：{str(e)}',
            'data': []
        }), 500


@reports_bp.route('/data-sources', methods=['GET'])
@log_api_call("reports.get_data_sources")
def get_data_sources():
    """获取数据源列表（仅mysql和starrocks）"""
    try:
        service_type = request.args.get('service_type')  # mysql或starrocks
        config_ids = request.args.get('config_ids')  # 逗号分隔的ID列表
        
        # 解析config_ids
        config_ids_list = None
        if config_ids:
            try:
                config_ids_list = [int(id.strip()) for id in config_ids.split(',')]
            except:
                pass
        
        configs = DataSourceService.get_configs(
            service_type=service_type,
            config_ids=config_ids_list
        )
        
        # 格式化返回数据
        result = []
        for config in configs:
            result.append({
                'id': config['id'],
                'service_type': config['service_type'],
                'config_name': config['config_name'],
                'host': config['host'],
                'port': config['port'],
                'database': config.get('database', ''),
                'description': config.get('description', '')
            })
        
        return jsonify({
            'success': True,
            'message': 'ok',
            'data': result
        })
    except Exception as e:
        current_app.logger.error(f"获取数据源列表失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'获取数据源列表失败：{str(e)}',
            'data': []
        }), 500


@reports_bp.route('/data-sources/<int:config_id>', methods=['GET'])
@log_api_call("reports.get_data_source")
def get_data_source(config_id):
    """获取单个数据源配置"""
    try:
        config = DataSourceService.get_config(config_id)
        
        return jsonify({
            'success': True,
            'message': 'ok',
            'data': {
                'id': config['id'],
                'service_type': config['service_type'],
                'config_name': config['config_name'],
                'host': config['host'],
                'port': config['port'],
                'username': config['username'],
                'database': config.get('database', ''),
                'description': config.get('description', '')
            }
        })
    except ValueError as e:
        return jsonify({
            'success': False,
            'message': str(e),
            'data': None
        }), 404
    except Exception as e:
        current_app.logger.error(f"获取数据源配置失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'获取数据源配置失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/data-sources/<int:config_id>/test', methods=['POST'])
@log_api_call("reports.test_data_source")
def test_data_source(config_id):
    """测试数据源连接"""
    try:
        result = DataSourceService.test_connection(config_id)
        
        if result['success']:
            return jsonify({
                'success': True,
                'message': result['message'],
                'data': result.get('config')
            })
        else:
            return jsonify({
                'success': False,
                'message': result['message'],
                'data': None
            }), 400
    except Exception as e:
        current_app.logger.error(f"测试数据源连接失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'测试连接失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/starrocks-databases', methods=['GET'])
@log_api_call("reports.get_starrocks_databases")
def get_starrocks_databases():
    """从MySQL的starrocks_tables表获取数据库列表"""
    try:
        config_id = request.args.get('config_id', type=int)
        
        if not config_id:
            return jsonify({
                'success': False,
                'message': '缺少参数: config_id',
                'data': []
            }), 400
        
        # 验证数据源配置是否存在且为starrocks类型
        try:
            config = DataSourceService.get_config(config_id)
            if config['service_type'] != 'starrocks':
                return jsonify({
                    'success': False,
                    'message': f'数据源配置 {config_id} 不是StarRocks类型',
                    'data': []
                }), 400
        except ValueError as e:
            return jsonify({
                'success': False,
                'message': str(e),
                'data': []
            }), 404
        
        # 从MySQL的starrocks_tables表获取数据库列表
        conn = get_db_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        
        try:
            sql = """
                SELECT DISTINCT database_name
                FROM starrocks_tables
                WHERE config_id = %s
                ORDER BY database_name
            """
            cursor.execute(sql, (config_id,))
            databases = cursor.fetchall()
            
            result = [db['database_name'] for db in databases]
            
            return jsonify({
                'success': True,
                'message': 'ok',
                'data': result
            })
        finally:
            cursor.close()
            conn.close()
            
    except Exception as e:
        current_app.logger.error(f"获取数据库列表失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'获取数据库列表失败：{str(e)}',
            'data': []
        }), 500


@reports_bp.route('/starrocks-tables', methods=['GET'])
@log_api_call("reports.get_starrocks_tables")
def get_starrocks_tables():
    """从MySQL的starrocks_tables表获取表列表"""
    try:
        config_id = request.args.get('config_id', type=int)
        database = request.args.get('database')
        
        if not config_id:
            return jsonify({
                'success': False,
                'message': '缺少参数: config_id',
                'data': []
            }), 400
        
        # 验证数据源配置是否存在且为starrocks类型
        try:
            config = DataSourceService.get_config(config_id)
            if config['service_type'] != 'starrocks':
                return jsonify({
                    'success': False,
                    'message': f'数据源配置 {config_id} 不是StarRocks类型',
                    'data': []
                }), 400
        except ValueError as e:
            return jsonify({
                'success': False,
                'message': str(e),
                'data': []
            }), 404
        
        # 从MySQL的starrocks_tables表获取表列表
        conn = get_db_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        
        try:
            conditions = ["config_id = %s"]
            params = [config_id]
            
            if database:
                conditions.append("database_name = %s")
                params.append(database)
            
            sql = f"""
                SELECT id, database_name, table_name, table_comment, 
                       engine, table_type, is_partitioned, total_columns
                FROM starrocks_tables
                WHERE {' AND '.join(conditions)}
                ORDER BY database_name, table_name
            """
            cursor.execute(sql, params)
            tables = cursor.fetchall()
            
            # 格式化返回数据
            result = []
            for table in tables:
                result.append({
                    'id': table['id'],
                    'database_name': table['database_name'],
                    'table_name': table['table_name'],
                    'table_comment': table.get('table_comment', ''),
                    'engine': table.get('engine', ''),
                    'table_type': table.get('table_type', ''),
                    'is_partitioned': bool(table.get('is_partitioned', 0)),
                    'total_columns': table.get('total_columns', 0)
                })
            
            return jsonify({
                'success': True,
                'message': 'ok',
                'data': result
            })
        finally:
            cursor.close()
            conn.close()
            
    except Exception as e:
        current_app.logger.error(f"获取表列表失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'获取表列表失败：{str(e)}',
            'data': []
        }), 500


@reports_bp.route('/kafka-topics', methods=['GET'])
@log_api_call("reports.get_kafka_topics")
def get_kafka_topics():
    """从MySQL的kafka_topic_monitor表获取Topic列表"""
    try:
        config_id = request.args.get('config_id', type=int)
        
        # 从MySQL的kafka_topic_monitor表获取Topic列表
        conn = get_db_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        
        try:
            if config_id:
                sql = """
                    SELECT DISTINCT topic_name
                    FROM kafka_topic_monitor
                    WHERE config_id = %s
                    ORDER BY topic_name
                """
                cursor.execute(sql, (config_id,))
            else:
                # 如果没有指定config_id，返回所有topic
                sql = """
                    SELECT DISTINCT topic_name
                    FROM kafka_topic_monitor
                    ORDER BY topic_name
                """
                cursor.execute(sql)
            
            topics = cursor.fetchall()
            result = [t['topic_name'] for t in topics]
            
            return jsonify({
                'success': True,
                'message': 'ok',
                'data': result
            })
        finally:
            cursor.close()
            conn.close()
            
    except Exception as e:
        current_app.logger.error(f"获取Kafka Topic列表失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'获取Topic列表失败：{str(e)}',
            'data': []
        }), 500


@reports_bp.route('/starrocks-table-fields', methods=['GET'])
@log_api_call("reports.get_starrocks_table_fields")
def get_starrocks_table_fields():
    """获取StarRocks表的字段列表"""
    try:
        config_id = request.args.get('config_id', type=int)
        database = request.args.get('database')
        table_name = request.args.get('table_name')
        
        if not all([config_id, database, table_name]):
            return jsonify({
                'success': False,
                'message': '缺少参数: config_id, database, table_name',
                'data': []
            }), 400
        
        # 验证数据源配置是否存在且为starrocks类型
        try:
            config = DataSourceService.get_config(config_id)
            if config['service_type'] != 'starrocks':
                return jsonify({
                    'success': False,
                    'message': f'数据源配置 {config_id} 不是StarRocks类型',
                    'data': []
                }), 400
        except ValueError as e:
            return jsonify({
                'success': False,
                'message': str(e),
                'data': []
            }), 404
        
        # 先从MySQL的starrocks_table_columns表查询
        conn = get_db_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        
        try:
            # 先查找表ID
            cursor.execute("""
                SELECT id FROM starrocks_tables
                WHERE config_id = %s AND database_name = %s AND table_name = %s
            """, (config_id, database, table_name))
            table = cursor.fetchone()
            
            fields = []
            if table:
                # 从starrocks_table_columns表查询字段
                cursor.execute("""
                    SELECT column_name, data_type, column_type, is_nullable, 
                           column_default, column_comment
                    FROM starrocks_table_columns
                    WHERE table_id = %s
                    ORDER BY ordinal_position
                """, (table['id'],))
                fields = cursor.fetchall()
            
            # 如果MySQL中没有字段信息，从StarRocks实时查询
            if not fields:
                try:
                    with DataSourceService.get_cursor(config_id) as sr_cursor:
                        sr_cursor.execute("""
                            SELECT 
                                COLUMN_NAME as column_name,
                                DATA_TYPE as data_type,
                                COLUMN_TYPE as column_type,
                                IS_NULLABLE as is_nullable,
                                COLUMN_DEFAULT as column_default,
                                COLUMN_COMMENT as column_comment
                            FROM information_schema.COLUMNS
                            WHERE TABLE_SCHEMA = %s AND TABLE_NAME = %s
                            ORDER BY ORDINAL_POSITION
                        """, (database, table_name))
                        fields = sr_cursor.fetchall()
                except Exception as e:
                    current_app.logger.warning(f"从StarRocks实时查询字段失败: {str(e)}")
            
            # 格式化返回数据
            result = []
            for field in fields:
                result.append({
                    'field_name': field.get('column_name', ''),
                    'data_type': field.get('data_type', ''),
                    'column_type': field.get('column_type', ''),
                    'is_nullable': field.get('is_nullable', 'YES'),
                    'column_default': field.get('column_default'),
                    'column_comment': field.get('column_comment', '')
                })
            
            return jsonify({
                'success': True,
                'message': 'ok',
                'data': result
            })
        finally:
            cursor.close()
            conn.close()
            
    except Exception as e:
        current_app.logger.error(f"获取表字段失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'获取表字段失败：{str(e)}',
            'data': []
        }), 500


# ==================== 模板管理 ====================

@reports_bp.route('/templates', methods=['GET'])
def get_templates():
    """获取报表模板列表"""
    try:
        group = request.args.get('group')
        is_active = request.args.get('is_active')
        page = request.args.get('page', type=int)
        size = request.args.get('size', type=int)
        sort_field = request.args.get('sort_field', 'template_group')
        sort_order = request.args.get('sort_order', 'asc')
        
        if is_active is not None:
            is_active = is_active.lower() == 'true'
        
        templates = ReportTemplateService.get_all_templates(
            group=group, 
            is_active=is_active,
            page=page,
            page_size=size,
            sort_field=sort_field,
            sort_order=sort_order
        )
        templates = [build_template_payload(tpl) for tpl in templates]

        return jsonify({
            'success': True,
            'message': 'ok',
            'data': {
                'rows': templates,
                'total': len(templates),
                'page': page,
                'size': size
            }
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'获取模板列表失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/templates/<template_code>', methods=['GET'])
def get_template_by_code(template_code):
    """根据编码获取模板详情"""
    try:
        template = ReportTemplateService.get_template_by_code(template_code)
        
        if not template:
            return jsonify({
                'success': False,
                'message': '模板不存在',
                'data': None
            }), 404

        template = build_template_payload(template)

        return jsonify({
            'success': True,
            'message': 'ok',
            'data': template
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'获取模板失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/templates/id/<int:template_id>', methods=['GET'])
def get_template_by_id(template_id):
    """根据ID获取模板详情"""
    try:
        template = ReportTemplateService.get_template_by_id(template_id)
        
        if not template:
            return jsonify({
                'success': False,
                'message': '模板不存在',
                'data': None
            }), 404

        template = build_template_payload(template)

        return jsonify({
            'success': True,
            'message': 'ok',
            'data': template
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'获取模板失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/templates/id/<int:template_id>', methods=['PUT'])
def update_template(template_id):
    """更新模板（仅允许更新有限字段）"""
    try:
        data = request.get_json()
        
        # 仅允许更新 template_name, description, is_active
        update_fields = {}
        for field in ['template_name', 'description', 'is_active']:
            if field in data:
                update_fields[field] = data[field]
        
        if not update_fields:
            return jsonify({
                'success': False,
                'message': '没有可更新的字段',
                'data': None
            }), 400
        
        success = ReportTemplateService.update_template(template_id, **update_fields)
        
        if not success:
            return jsonify({
                'success': False,
                'message': '模板不存在',
                'data': None
            }), 404
        
        return jsonify({
            'success': True,
            'message': '更新成功',
            'data': None
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'更新模板失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/templates/<template_code>/platforms', methods=['GET'])
@log_api_call("reports.get_template_platforms")
def get_template_platforms(template_code):
    """获取模板的平台列表（供前端使用）"""
    try:
        provider = get_provider(template_code, {})
        if not provider:
            return jsonify({
                'success': False,
                'message': '模板不存在或未注册',
                'data': []
            }), 404
        
        # 检查 provider 是否有 get_platform_list 方法
        if hasattr(provider, 'get_platform_list'):
            platforms = provider.get_platform_list()
            return jsonify({
                'success': True,
                'message': 'ok',
                'data': platforms
            })
        else:
            return jsonify({
                'success': False,
                'message': '该模板不支持平台列表',
                'data': []
            }), 400
    except Exception as e:
        current_app.logger.error(f"获取模板平台列表失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'获取平台列表失败：{str(e)}',
            'data': []
        }), 500


# ==================== 配置管理 ====================

@reports_bp.route('/configs', methods=['GET'])
def get_configs():
    """获取报表配置列表"""
    try:
        template_code = request.args.get('template_code')
        is_active = request.args.get('is_active')
        schedule_enabled = request.args.get('schedule_enabled')
        page = request.args.get('page', 1, type=int)
        size = request.args.get('size', 20, type=int)
        sort_field = request.args.get('sort_field', 'created_at')
        sort_order = request.args.get('sort_order', 'desc')
        # 新增：按任務ID與是否包含一次性運行的過濾
        report_config_id = request.args.get('report_config_id', type=int)
        include_all = request.args.get('include_all', default=0, type=int)
        report_config_id = request.args.get('report_config_id', type=int)
        include_all = request.args.get('include_all', default=0, type=int)
        
        if is_active is not None:
            is_active = is_active.lower() == 'true'
        
        if schedule_enabled is not None:
            schedule_enabled = schedule_enabled.lower() == 'true'
        
        configs = ReportConfigService.get_all_configs(
            template_code=template_code,
            is_active=is_active,
            schedule_enabled=schedule_enabled,
            sort_field=sort_field,
            sort_order=sort_order
        )
        
        # 注意：ReportConfigService.get_all_configs 已经解析了JSON字段，不需要再次解析
        
        # 分页处理
        total = len(configs)
        start = (page - 1) * size
        end = start + size
        configs_page = configs[start:end]
        configs_page = [build_config_payload(cfg) for cfg in configs_page]
        
        return jsonify({
            'success': True,
            'message': 'ok',
            'data': {
                'rows': configs_page,
                'total': total,
                'page': page,
                'size': size
            }
        })
    except Exception as e:
        # 检查是否为数据库连接错误
        is_conn_error, friendly_msg = is_database_connection_error(e)
        
        if is_conn_error:
            current_app.logger.error(f"获取配置列表失败（数据库连接错误）: {str(e)}", exc_info=True)
            return jsonify({
                'success': False,
                'message': friendly_msg,
                'data': None
            }), 500
        else:
            current_app.logger.error(f"获取配置列表失败: {str(e)}", exc_info=True)
            return jsonify({
                'success': False,
                'message': f'获取配置列表失败：{str(e)}',
                'data': None
            }), 500


@reports_bp.route('/configs/<int:config_id>', methods=['GET'])
def get_config(config_id):
    """获取单个配置详情"""
    try:
        config = ReportConfigService.get_config_by_id(config_id)
        
        if not config:
            return jsonify({
                'success': False,
                'message': '配置不存在',
                'data': None
            }), 404
        
        config = build_config_payload(config)
        
        try:
            notifications = ReportNotificationConfigService.get_notifications_by_config(config_id)
            notifications = [build_notification_payload(notif) for notif in notifications]
        except Exception as e:
            # 获取通知配置失败，记录日志但不影响主配置返回
            current_app.logger.warning(f"获取配置通知信息失败: {str(e)}, 配置ID: {config_id}")
            notifications = []
        
        config['notifications'] = notifications
        
        return jsonify({
            'success': True,
            'message': 'ok',
            'data': config
        })
    except Exception as e:
        # 检查是否为数据库连接错误
        is_conn_error, friendly_msg = is_database_connection_error(e)
        
        if is_conn_error:
            current_app.logger.error(f"获取配置详情失败（数据库连接错误）: {str(e)}", exc_info=True)
            return jsonify({
                'success': False,
                'message': friendly_msg,
                'data': None
            }), 500
        else:
            current_app.logger.error(f"获取配置详情失败: {str(e)}", exc_info=True)
            return jsonify({
                'success': False,
                'message': f'获取配置详情失败：{str(e)}',
                'data': None
            }), 500


@reports_bp.route('/configs', methods=['POST'])
def create_config():
    """创建报表配置"""
    try:
        data = request.get_json()
        
        config_id = ReportConfigService.create_config(
            config_name=data['config_name'],
            template_code=data['template_code'],
            params=data['params'],
            output_formats=data['output_formats'],
            schedule_enabled=data.get('schedule_enabled', False),
            schedule_cron=data.get('schedule_cron'),
            notify_on_success=data.get('notify_on_success', False)
        )
        
        # 创建通知配置
        if data.get('notifications'):
            for notif in data['notifications']:
                ReportNotificationConfigService.create_notification(
                    config_id=config_id,
                    channel=notif['channel'],
                    enabled=notif.get('enabled', True),
                    wecom_webhook_url=notif.get('wecom_webhook_url'),
                    wecom_mention=notif.get('wecom_mention'),
                    email_recipients=notif.get('email_recipients'),
                    email_cc=notif.get('email_cc')
                )
        
        # 如果启用了定时任务，同步到调度器
        if data.get('schedule_enabled'):
            scheduler = get_report_scheduler()
            if scheduler:
                scheduler.add_config_job(config_id)
        
        return jsonify({
            'success': True,
            'message': '创建成功',
            'data': {'id': config_id}
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'创建配置失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/configs/<int:config_id>', methods=['PUT'])
def update_config(config_id):
    """更新报表配置"""
    try:
        data = request.get_json()
        
        # 更新配置基本信息
        update_fields = {}
        for field in ['config_name', 'params', 'output_formats', 
                     'schedule_enabled', 'schedule_cron', 
                     'notify_on_success', 'is_active']:
            if field in data:
                update_fields[field] = data[field]
        
        if update_fields:
            ReportConfigService.update_config(config_id, **update_fields)
        
        # 更新通知配置
        if 'notifications' in data:
            # 先删除旧的通知配置
            ReportNotificationConfigService.delete_notifications_by_config(config_id)
            
            # 创建新的通知配置
            for notif in data['notifications']:
                # 如果提供了 webhook_id，直接使用；否则使用 channel 和 webhook_url 创建
                if notif.get('webhook_id'):
                    # 即使选择了已存在的 webhook，也需要传递 wecom_mention 来更新 @ 成员配置
                    ReportNotificationConfigService.create_notification(
                        config_id=config_id,
                        webhook_id=notif['webhook_id'],
                        enabled=notif.get('enabled', True),
                        wecom_mention=notif.get('wecom_mention'),  # 传递 wecom_mention 以更新 webhook 库
                        email_recipients=notif.get('email_recipients'),
                        email_cc=notif.get('email_cc')
                    )
                else:
                    ReportNotificationConfigService.create_notification(
                        config_id=config_id,
                        channel=notif['channel'],
                        enabled=notif.get('enabled', True),
                        wecom_webhook_url=notif.get('wecom_webhook_url'),
                        wecom_mention=notif.get('wecom_mention'),
                        email_recipients=notif.get('email_recipients'),
                        email_cc=notif.get('email_cc')
                    )
        
        # 同步到调度器
        scheduler = get_report_scheduler()
        if scheduler:
            scheduler.update_config_job(config_id)
        
        return jsonify({
            'success': True,
            'message': '更新成功',
            'data': None
        })
    except Exception as e:
        import traceback
        error_msg = str(e)
        error_trace = traceback.format_exc()
        current_app.logger.error(f"更新配置失败: {error_msg}\n{error_trace}")
        return jsonify({
            'success': False,
            'message': f'更新配置失败：{error_msg}',
            'data': None
        }), 500


@reports_bp.route('/configs/<int:config_id>', methods=['DELETE'])
def delete_config(config_id):
    """删除报表配置"""
    try:
        # 先从调度器中移除
        scheduler = get_report_scheduler()
        if scheduler:
            scheduler.remove_config_job(config_id)
        
        success = ReportConfigService.delete_config(config_id)
        
        if not success:
            return jsonify({
                'success': False,
                'message': '配置不存在',
                'data': None
            }), 404
        
        return jsonify({
            'success': True,
            'message': '删除成功',
            'data': None
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'删除配置失败：{str(e)}',
            'data': None
        }), 500


# ==================== 报表生成 ====================

@reports_bp.route('/preview-sql', methods=['POST'])
@log_api_call("reports.preview_sql")
def preview_sql():
    """预览SQL（不执行）"""
    try:
        data = request.get_json()
        template_code = data.get('template_code')
        params = data.get('params', {})
        
        if not template_code:
            return jsonify({
                'success': False,
                'message': '模板代码不能为空',
                'data': None
            }), 400
        
        # 获取provider实例
        provider = get_provider(template_code, params)
        
        # 如果是DataQualityMonitorProvider，调用预览方法
        if hasattr(provider, 'preview_sql'):
            sql = provider.preview_sql()
        else:
            # 对于其他provider，尝试调用collect但不执行
            # 这里我们需要一个更通用的方法
            # 暂时返回错误，提示需要实现preview_sql方法
            return jsonify({
                'success': False,
                'message': '该模板不支持SQL预览',
                'data': None
            }), 400
        
        return jsonify({
            'success': True,
            'message': 'ok',
            'data': {
                'sql': sql
            }
        })
    except Exception as e:
        current_app.logger.error(f"预览SQL失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'预览SQL失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/generate', methods=['POST'])
def generate_report():
    """手动生成报表（后台异步执行）"""
    import threading
    import logging
    
    try:
        data = request.get_json()
        template_code = data['template_code']
        params = data['params']
        output_formats = data.get('output_formats', ['html'])
        notify_params = data.get('notify_params')  # 临时覆盖通知参数
        title = data.get('title')  # 报表标题
        config_id = data.get('config_id')  # 配置ID（可选，用于关联报表任务）
        if config_id is not None:
            config_id = int(config_id)  # 确保为整数类型
        
        # 在请求上下文中获取应用实例（需要在请求上下文中调用）
        app = current_app._get_current_object()
        logger = logging.getLogger('flink.business')
        
        orchestrator = ReportOrchestrator()
        
        # 创建运行记录（状态为queued）
        # 注意：立即运行应该始终使用 'manual' 作为 trigger_type，不受配置的 schedule_enabled 影响
        run_id = orchestrator.create_run(
            template_code=template_code,
            params=params,
            config_id=config_id,  # 传递配置ID，用于关联报表任务
            trigger_type='manual',  # 立即运行始终是手动触发
            title=title,
            output_formats=output_formats  # 传递输出格式
        )
        
        # 记录日志，确保 trigger_type 正确
        logger.info(f"立即运行报表: run_id={run_id}, config_id={config_id}, trigger_type=manual, template_code={template_code}")
        
        # 记录任务提交
        logger.info(f"提交报表生成任务，run_id: {run_id}, template_code: {template_code}")
        print(f"[PRINT] 提交报表生成任务，run_id: {run_id}, template_code: {template_code}", flush=True)
        
        # 定义后台任务函数
        def generate_report_task():
            """后台执行报表生成任务"""
            # 在函数最开始就设置logger，确保即使出错也能记录
            import sys
            task_logger = None
            try:
                # 使用app.logger作为business logger
                task_logger = app.logger
            except:
                # 如果logger无法创建，使用print作为备用
                print(f"[ERROR] 无法创建logger，run_id: {run_id}", file=sys.stderr)
            
            task_orchestrator = None
            try:
                # 记录线程启动
                print(f"[PRINT] 后台线程已启动，run_id: {run_id}", flush=True)
                if task_logger:
                    task_logger.info(f"[后台线程] 线程已启动，准备执行报表生成任务，run_id: {run_id}")
                else:
                    print(f"[INFO] 后台线程已启动，run_id: {run_id}", file=sys.stderr)
                
                # 使用应用实例创建新的应用上下文
                with app.app_context():
                    if task_logger:
                        task_logger.info(f"[后台线程] 应用上下文已创建，开始执行报表生成任务，run_id: {run_id}")
                    
                    # 在应用上下文中重新创建orchestrator
                    task_orchestrator = ReportOrchestrator()
                    if task_logger:
                        task_logger.info(f"[后台线程] Orchestrator已创建，run_id: {run_id}")
                    
                    # 更新状态为运行中
                    if task_logger:
                        task_logger.info(f"[后台线程] 更新状态为running，run_id: {run_id}")
                    task_orchestrator.update_run_status(run_id, 'running', progress=5)
                    if task_logger:
                        task_logger.info(f"[后台线程] 状态更新成功，run_id: {run_id}")
                    
                    # 获取数据提供者
                    if task_logger:
                        task_logger.info(f"[后台线程] 获取数据提供者，run_id: {run_id}")
                    provider = get_provider(template_code, params)
                    if task_logger:
                        task_logger.info(f"[后台线程] 数据提供者获取成功，run_id: {run_id}")
                    
                    # 先收集数据（所有格式共享）
                    task_orchestrator.update_run_status(run_id, 'running', progress=10)
                    if task_logger:
                        task_logger.info(f"[后台线程] 开始收集数据，run_id: {run_id}")
                    print(f"[PRINT] 开始收集数据，run_id: {run_id}", flush=True)
                    data = provider.collect()
                    print(f"[PRINT] 数据收集完成，run_id: {run_id}", flush=True)
                    if task_logger:
                        task_logger.info(f"[后台线程] 数据收集完成，run_id: {run_id}")
                    task_orchestrator.update_run_status(run_id, 'running', progress=30)
                    
                    # 获取运行记录以获取标题和时间范围
                    from app.services.report_service import ReportRunService
                    run_record = ReportRunService.get_run_by_id(run_id)
                    report_title = run_record.get('title') if run_record else None
                    time_start = run_record.get('time_start') if run_record else None
                    time_end = run_record.get('time_end') if run_record else None
                    
                    # 生成各种格式的报表
                    total_formats = len(output_formats)
                    success_count = 0
                    failed_formats = []
                    
                    if task_logger:
                        task_logger.info(f"[后台线程] 开始生成报表，格式数量: {total_formats}，run_id: {run_id}")
                    for idx, file_type in enumerate(output_formats):
                        try:
                            if task_logger:
                                task_logger.info(f"[后台线程] 生成{file_type}格式报表 ({idx+1}/{total_formats})，run_id: {run_id}")
                            renderer = get_renderer(file_type, template_code, title=report_title, time_start=time_start, time_end=time_end)
                            # 更新进度（30% + 每个格式约60%）
                            progress = 30 + int((idx + 1) / total_formats * 60)
                            task_orchestrator.update_run_status(run_id, 'running', progress=progress)
                            
                            # 渲染报表（复用已收集的数据）
                            content = renderer.render(data)
                            if task_logger:
                                task_logger.info(f"[后台线程] {file_type}格式渲染完成，run_id: {run_id}")
                            
                            # 保存文件
                            file_type_str = renderer.get_file_type()
                            previewable = file_type_str == 'html'
                            task_orchestrator.save_file(run_id, file_type_str, content, previewable)
                            success_count += 1
                            if task_logger:
                                task_logger.info(f"[后台线程] 成功生成{file_type}格式报表，run_id: {run_id}")
                            
                        except Exception as e:
                            if task_logger:
                                task_logger.error(f"[后台线程] 生成{file_type}格式报表失败: {str(e)}", exc_info=True)
                            failed_formats.append(f"{file_type}: {str(e)}")
                            # 继续生成其他格式，不中断
                    
                    # 生成摘要（使用最后一次收集的数据）
                    if success_count > 0:
                        try:
                            if task_logger:
                                task_logger.info(f"[后台线程] 生成摘要，run_id: {run_id}")
                            summary = provider.generate_summary(data)
                            task_orchestrator.update_run_status(run_id, 'success', progress=100, summary=summary)
                            if task_logger:
                                task_logger.info(f"[后台线程] 报表生成完成，run_id: {run_id}, 成功: {success_count}/{total_formats}")
                        except Exception as e:
                            if task_logger:
                                task_logger.error(f"[后台线程] 生成摘要失败: {str(e)}", exc_info=True)
                            # 即使摘要失败，也标记为成功（因为文件已生成）
                            task_orchestrator.update_run_status(run_id, 'success', progress=100)
                            if task_logger:
                                task_logger.info(f"[后台线程] 报表生成完成（摘要失败但已标记成功），run_id: {run_id}")
                    else:
                        # 所有格式都失败
                        error_msg = f"所有格式生成失败: {', '.join(failed_formats)}"
                        task_orchestrator.update_run_status(run_id, 'failed', progress=0, error_message=error_msg)
                        if task_logger:
                            task_logger.error(f"[后台线程] 报表生成失败，run_id: {run_id}, 错误: {error_msg}")
                    
                    # 发送通知（如果配置了且至少有一个格式成功）
                    if success_count > 0 and notify_params and notify_params.get('send_notification'):
                        try:
                            if task_logger:
                                task_logger.info(f"[后台线程] 发送通知，run_id: {run_id}")
                            NotificationService.send_report_notification(run_id, notify_params)
                            if task_logger:
                                task_logger.info(f"[后台线程] 通知发送成功，run_id: {run_id}")
                        except Exception as e:
                            if task_logger:
                                task_logger.error(f"[后台线程] 发送通知失败: {str(e)}", exc_info=True)
                            # 通知失败不影响报表生成成功
                    
                    if task_logger:
                        task_logger.info(f"[后台线程] 报表生成任务完成，run_id: {run_id}")
                    
            except Exception as e:
                # 即使没有应用上下文，也尝试记录错误
                try:
                    task_logger.error(f"[后台线程] 后台生成报表失败，run_id: {run_id}, 错误: {str(e)}", exc_info=True)
                except:
                    # 如果logger也无法使用，使用print作为最后手段
                    import sys
                    print(f"[ERROR] 后台生成报表失败，run_id: {run_id}, 错误: {str(e)}", file=sys.stderr)
                    import traceback
                    traceback.print_exc()
                
                # 尝试更新状态
                try:
                    if task_orchestrator:
                        with app.app_context():
                            task_orchestrator.update_run_status(run_id, 'failed', progress=0, error_message=str(e))
                    else:
                        # 如果orchestrator不存在，创建新的
                        with app.app_context():
                            fallback_orchestrator = ReportOrchestrator()
                            fallback_orchestrator.update_run_status(run_id, 'failed', progress=0, error_message=str(e))
                except Exception as update_error:
                    try:
                        task_logger.error(f"[后台线程] 更新运行状态失败: {str(update_error)}", exc_info=True)
                    except:
                        import sys
                        print(f"[ERROR] 更新运行状态失败: {str(update_error)}", file=sys.stderr)
        
        # 在后台线程中执行报表生成
        logger.info(f"启动后台线程执行报表生成，run_id: {run_id}")
        thread = threading.Thread(target=generate_report_task, daemon=False, name=f"ReportGen-{run_id}")
        thread.start()
        logger.info(f"后台线程已启动，线程ID: {thread.ident}, run_id: {run_id}")
        
        # 立即返回，不等待任务完成
        return jsonify({
            'success': True,
            'message': '报表生成任务已提交，正在后台执行',
            'data': {'run_id': run_id}
        })
    except Exception as e:
        current_app.logger.error(f"提交报表生成任务失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'提交报表生成任务失败：{str(e)}',
            'data': None
        }), 500


# ==================== 运行历史 ====================

@reports_bp.route('/runs', methods=['GET'])
def get_runs():
    """获取运行历史列表"""
    try:
        template_code = request.args.get('template_code')
        status = request.args.get('status')
        trigger_type = request.args.get('trigger_type')
        start_date = request.args.get('start_date')
        end_date = request.args.get('end_date')
        page = request.args.get('page', 1, type=int)
        size = request.args.get('size', 20, type=int)
        sort_field = request.args.get('sort_field', 'created_at')
        sort_order = request.args.get('sort_order', 'desc')
        # 新增：支持按任务ID过滤与是否包含一次性运行
        report_config_id = request.args.get('report_config_id', type=int)
        include_all = request.args.get('include_all', default=0, type=int)
        # 新增：支持按运行ID过滤（精确匹配单条记录）
        run_id = request.args.get('run_id', type=int)
        
        # 计算offset和limit
        offset = (page - 1) * size
        limit = size
        
        runs = ReportRunService.get_runs(
            template_code=template_code,
            status=status,
            trigger_type=trigger_type,
            start_date=start_date,
            end_date=end_date,
            limit=limit,
            offset=offset,
            sort_field=sort_field,
            sort_order=sort_order,
            report_config_id=report_config_id,
            include_all=bool(include_all),
            run_id=run_id
        )
        
        runs = [build_run_payload(run) for run in runs]
        
        # 获取总数（需要在service层实现计数方法，这里简化处理）
        # 实际应该调用 ReportRunService.count_runs(template_code, status)
        total = len(runs) + offset if len(runs) == limit else offset + len(runs)
        
        return jsonify({
            'success': True,
            'message': 'ok',
            'data': {
                'rows': runs,
                'total': total,
                'page': page,
                'size': size
            }
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'获取运行历史失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/runs/<int:run_id>', methods=['GET'])
def get_run(run_id):
    """获取单个运行详情"""
    try:
        run = ReportRunService.get_run_by_id(run_id)
        
        if not run:
            return jsonify({
                'success': False,
                'message': '运行记录不存在',
                'data': None
            }), 404
        
        run = build_run_payload(run)
        
        # 获取文件列表
        files = ReportRunService.get_run_files(run_id)
        run['files'] = files
        
        return jsonify({
            'success': True,
            'message': 'ok',
            'data': run
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'获取运行详情失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/runs/<int:run_id>', methods=['DELETE'])
def delete_run(run_id):
    """删除运行记录及其报表文件"""
    try:
        deleted = ReportRunService.delete_run(run_id, remove_files=True)
        
        if not deleted:
            return jsonify({
                'success': False,
                'message': '运行记录不存在',
                'data': None
            }), 404
        
        return jsonify({
            'success': True,
            'message': '删除成功',
            'data': None
        })
    except Exception as e:
        current_app.logger.error(f"删除运行记录失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'删除运行失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/runs/<int:run_id>/files/<int:file_id>/download', methods=['GET'])
def download_file(run_id, file_id):
    """下载报表文件"""
    try:
        orchestrator = ReportOrchestrator()
        file_path = orchestrator.get_file_path(file_id)
        
        # 构建下载链接用于日志
        download_url = f"/api/reports/runs/{run_id}/files/{file_id}/download"
        request_url = request.url
        
        if not file_path:
            current_app.logger.error(
                f"下载文件失败 - 文件不存在 | "
                f"run_id: {run_id}, file_id: {file_id}, "
                f"download_url: {download_url}, request_url: {request_url}"
            )
            return jsonify({
                'success': False,
                'message': f'文件不存在（file_id: {file_id}）',
                'data': None
            }), 404
        
        if not file_path.exists():
            current_app.logger.error(
                f"下载文件失败 - 文件路径不存在 | "
                f"run_id: {run_id}, file_id: {file_id}, "
                f"file_path: {file_path}, "
                f"download_url: {download_url}, request_url: {request_url}"
            )
            return jsonify({
                'success': False,
                'message': f'文件不存在: {file_path}',
                'data': None
            }), 404
        
        # 记录成功的下载请求
        current_app.logger.info(
            f"下载文件成功 | "
            f"run_id: {run_id}, file_id: {file_id}, "
            f"file_path: {file_path}, file_name: {file_path.name}, "
            f"download_url: {download_url}, request_url: {request_url}"
        )
        
        return send_file(
            str(file_path),
            as_attachment=True,
            download_name=file_path.name
        )
    except Exception as e:
        current_app.logger.error(
            f"下载文件异常 | "
            f"run_id: {run_id}, file_id: {file_id}, "
            f"error: {str(e)}, "
            f"download_url: {request.url if hasattr(request, 'url') else 'N/A'}",
            exc_info=True
        )
        return jsonify({
            'success': False,
            'message': f'下载文件失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/runs/<int:run_id>/files/<int:file_id>/preview', methods=['GET'])
def preview_file(run_id, file_id):
    """预览报表文件（仅HTML）"""
    try:
        orchestrator = ReportOrchestrator()
        file_path = orchestrator.get_file_path(file_id)
        
        if not file_path or not file_path.exists():
            return jsonify({
                'success': False,
                'message': '文件不存在',
                'data': None
            }), 404
        
        # 只支持HTML预览
        if file_path.suffix.lower() != '.html':
            return jsonify({
                'success': False,
                'message': '该文件类型不支持预览',
                'data': None
            }), 400
        
        return send_file(file_path, mimetype='text/html')
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'预览文件失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/runs/<int:run_id>/share', methods=['GET'])
def share_report(run_id):
    """生成报表分享页面（HTML）"""
    try:
        from app.services.report_share_service import ReportShareService
        import os
        from flask import request
        
        # 获取基础URL（用于构建预览和下载链接）
        # 优先从环境变量获取
        base_url = os.getenv('BACKEND_URL') or os.getenv('API_BASE_URL') or os.getenv('FRONTEND_URL') or os.getenv('BASE_URL') or os.getenv('APP_URL')
        
        if not base_url:
            # 从请求URL提取基础URL（如果从环境变量获取失败）
            base_url = request.url_root.rstrip('/')
            if base_url.endswith('/api'):
                base_url = base_url[:-4]
        
        # 确保base_url不以斜杠结尾
        base_url = base_url.rstrip('/')
        
        # 生成分享页面
        html = ReportShareService.generate_share_page(run_id, base_url)
        
        return html, 200, {'Content-Type': 'text/html; charset=utf-8'}
        
    except Exception as e:
        current_app.logger.error(f"生成分享页面失败: {str(e)}", exc_info=True)
        # 返回错误页面
        error_html = f"""
        <!DOCTYPE html>
        <html lang="zh-CN">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>错误</title>
            <style>
                body {{ 
                    font-family: Arial, sans-serif; 
                    text-align: center; 
                    padding: 50px;
                    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                    min-height: 100vh;
                    display: flex;
                    align-items: center;
                    justify-content: center;
                }}
                .error-container {{
                    background: white;
                    padding: 40px;
                    border-radius: 12px;
                    box-shadow: 0 10px 40px rgba(0,0,0,0.2);
                }}
                .error {{ color: #dc3545; }}
            </style>
        </head>
        <body>
            <div class="error-container">
                <h1 class="error">生成分享页面失败</h1>
                <p>{str(e)}</p>
            </div>
        </body>
        </html>
        """
        return error_html, 500


# ==================== 通知管理 ====================

@reports_bp.route('/configs/<int:config_id>/notifications', methods=['GET'])
def get_config_notifications(config_id):
    """获取配置的通知列表"""
    try:
        notifications = ReportNotificationConfigService.get_notifications_by_config(config_id)
        notifications = [build_notification_payload(notif) for notif in notifications]
        
        return jsonify({
            'success': True,
            'message': 'ok',
            'data': {
                'rows': notifications,
                'total': len(notifications)
            }
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'获取通知配置失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/configs/<int:config_id>/notifications', methods=['POST'])
def create_config_notification(config_id):
    """为配置创建通知"""
    try:
        data = request.get_json()
        
        notification_id = ReportNotificationConfigService.create_notification(
            config_id=config_id,
            channel=data['channel'],
            enabled=data.get('enabled', True),
            wecom_webhook_url=data.get('wecom_webhook_url'),
            wecom_mention=data.get('wecom_mention'),
            email_recipients=data.get('email_recipients'),
            email_cc=data.get('email_cc')
        )
        
        return jsonify({
            'success': True,
            'message': '创建成功',
            'data': {'id': notification_id}
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'创建通知配置失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/notifications/<int:notification_id>', methods=['PUT'])
def update_notification(notification_id):
    """更新通知配置"""
    try:
        data = request.get_json()
        
        success = ReportNotificationConfigService.update_notification(
            notification_id, **data
        )
        
        if not success:
            return jsonify({
                'success': False,
                'message': '通知配置不存在',
                'data': None
            }), 404
        
        return jsonify({
            'success': True,
            'message': '更新成功',
            'data': None
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'更新通知配置失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/notifications/<int:notification_id>', methods=['DELETE'])
def delete_notification(notification_id):
    """删除通知配置"""
    try:
        success = ReportNotificationConfigService.delete_notification(notification_id)
        
        if not success:
            return jsonify({
                'success': False,
                'message': '通知配置不存在',
                'data': None
            }), 404
        
        return jsonify({
            'success': True,
            'message': '删除成功',
            'data': None
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'删除通知配置失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/runs/<int:run_id>/notify', methods=['POST'])
def resend_notification(run_id):
    """重新发送通知"""
    try:
        data = request.get_json()
        notify_params = data.get('notify_params')
        
        NotificationService.send_report_notification(run_id, notify_params)
        
        return jsonify({
            'success': True,
            'message': '通知发送成功',
            'data': None
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'发送通知失败：{str(e)}',
            'data': None
        }), 500


# ==================== 通知管理（独立视图） ====================

@reports_bp.route('/notifications/list', methods=['GET'])
def get_all_notifications():
    """获取所有通知配置列表（跨配置查询）"""
    try:
        config_id = request.args.get('config_id', type=int)
        channel = request.args.get('channel')
        enabled = request.args.get('enabled')
        page = request.args.get('page', 1, type=int)
        size = request.args.get('size', 20, type=int)
        sort_field = request.args.get('sort_field', 'created_at')
        sort_order = request.args.get('sort_order', 'desc')
        
        conn = get_db_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        
        try:
            # 构建查询条件
            conditions = []
            params = []
            
            if config_id:
                conditions.append("n.report_config_id = %s")
                params.append(config_id)
            
            if channel:
                conditions.append("n.channel = %s")
                params.append(channel)
            
            if enabled is not None:
                enabled_val = 1 if enabled.lower() == 'true' else 0
                conditions.append("n.enabled = %s")
                params.append(enabled_val)
            
            where_clause = " AND ".join(conditions) if conditions else "1=1"
            
            # 获取总数
            count_sql = f"""
                SELECT COUNT(*) as total 
                FROM report_config_notifications n
                WHERE {where_clause}
            """
            cursor.execute(count_sql, params)
            total = cursor.fetchone()['total']
            
            # 获取数据（JOIN report_configs 获取配置名称和模板信息）
            allowed_sort_fields = ['id', 'report_config_id', 'channel', 'enabled', 'created_at', 'updated_at']
            if sort_field not in allowed_sort_fields:
                sort_field = 'created_at'
            sort_order_sql = 'DESC' if sort_order.lower() == 'desc' else 'ASC'
            
            offset = (page - 1) * size
            
            data_sql = f"""
                SELECT 
                    n.*,
                    c.config_name,
                    c.template_code
                FROM report_config_notifications n
                LEFT JOIN report_configs c ON n.report_config_id = c.id
                WHERE {where_clause}
                ORDER BY n.{sort_field} {sort_order_sql}
                LIMIT %s OFFSET %s
            """
            params.extend([size, offset])
            cursor.execute(data_sql, params)
            rows = cursor.fetchall()
            
            rows = [build_notification_payload(row) for row in rows]
            
            return jsonify({
                'success': True,
                'message': 'ok',
                'data': {
                    'rows': rows,
                    'total': total,
                    'page': page,
                    'size': size
                }
            })
        finally:
            cursor.close()
            conn.close()
    except Exception as e:
        current_app.logger.error(f"获取通知配置列表失败: {str(e)}")
        return jsonify({
            'success': False,
            'message': f'获取通知配置列表失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/notifications/<int:notification_id>', methods=['GET'])
def get_notification_detail(notification_id):
    """获取单个通知配置详情"""
    try:
        conn = get_db_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        
        try:
            sql = """
                SELECT 
                    n.*,
                    c.config_name,
                    c.template_code,
                    t.template_name
                FROM report_config_notifications n
                LEFT JOIN report_configs c ON n.report_config_id = c.id
                LEFT JOIN report_templates t ON c.template_code = t.template_code
                WHERE n.id = %s
            """
            cursor.execute(sql, (notification_id,))
            notification = cursor.fetchone()
            
            if not notification:
                return jsonify({
                    'success': False,
                    'message': '通知配置不存在',
                    'data': None
                }), 404
            
            notification = build_notification_payload(notification)
            
            return jsonify({
                'success': True,
                'message': 'ok',
                'data': notification
            })
        finally:
            cursor.close()
            conn.close()
    except Exception as e:
        current_app.logger.error(f"获取通知配置详情失败: {str(e)}")
        return jsonify({
            'success': False,
            'message': f'获取通知配置详情失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/notifications/<int:notification_id>/toggle', methods=['POST'])
def toggle_notification_status(notification_id):
    """快速切换通知启用状态"""
    try:
        success = ReportNotificationConfigService.update_notification(
            notification_id, 
            enabled=request.get_json().get('enabled', True)
        )
        
        if not success:
            return jsonify({
                'success': False,
                'message': '通知配置不存在',
                'data': None
            }), 404
        
        return jsonify({
            'success': True,
            'message': '状态更新成功',
            'data': None
        })
    except Exception as e:
        current_app.logger.error(f"切换通知状态失败: {str(e)}")
        return jsonify({
            'success': False,
            'message': f'切换通知状态失败：{str(e)}',
            'data': None
        }), 500


# ==================== Webhook管理 ====================

@reports_bp.route('/webhooks', methods=['GET'])
@log_api_call("reports.get_webhooks")
def get_webhooks():
    """获取系统中所有可用的Webhook地址列表"""
    conn = None
    cursor = None
    
    try:
        conn = get_db_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        
        webhooks = []
        
        # 从webhook地址库获取
        try:
            cursor.execute("""
                SELECT 
                    id, 
                    name, 
                    channel_type,
                    webhook_url, 
                    description,
                    wecom_mention,
                    email_recipients
                FROM report_webhook_library
                WHERE is_active = 1
                ORDER BY created_at DESC
            """)
            library_webhooks = cursor.fetchall()
            
            for row in library_webhooks:
                webhook_data = {
                    'id': row['id'],
                    'name': row['name'],
                    'channel_type': row['channel_type'],
                    'description': row.get('description')
                }
                
                # 根据渠道类型返回对应的字段
                if row['channel_type'] in ('wecom', 'dingtalk', 'feishu', 'custom'):
                    webhook_data['url'] = row['webhook_url']
                    if row['channel_type'] == 'wecom' and row.get('wecom_mention'):
                        webhook_data['wecom_mention'] = json.loads(row['wecom_mention']) if isinstance(row['wecom_mention'], str) else row['wecom_mention']
                elif row['channel_type'] == 'email':
                    if row.get('email_recipients'):
                        webhook_data['email_recipients'] = json.loads(row['email_recipients']) if isinstance(row['email_recipients'], str) else row['email_recipients']
                
                webhooks.append(webhook_data)
                
            current_app.logger.info(f"获取到 {len(webhooks)} 个可用的Webhook地址")
        except Exception as e:
            current_app.logger.warning(f"从webhook库获取失败: {str(e)}")
        
        return jsonify({
            'success': True,
            'message': 'ok',
            'data': webhooks
        })
        
    except Exception as e:
        current_app.logger.error(f"获取Webhook列表失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'获取Webhook列表失败：{str(e)}',
            'data': []
        }), 500
        
    finally:
        if cursor:
            cursor.close()
        if conn:
            conn.close()


# ==================== Webhook库管理 CRUD ====================

@reports_bp.route('/webhook-library', methods=['GET'])
@log_api_call("reports.get_webhook_library")
def get_webhook_library():
    """获取webhook地址库列表"""
    try:
        name = request.args.get('name')
        channel_type = request.args.get('channel_type')
        is_active = request.args.get('is_active')
        page = request.args.get('page', 1, type=int)
        size = request.args.get('size', 20, type=int)
        
        conn = get_db_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        
        try:
            # 构建查询条件
            conditions = []
            params = []
            
            if name:
                conditions.append("name LIKE %s")
                params.append(f"%{name}%")
            
            if channel_type:
                conditions.append("channel_type = %s")
                params.append(channel_type)
            
            if is_active is not None:
                conditions.append("is_active = %s")
                params.append(1 if is_active.lower() == 'true' else 0)
            
            where_clause = " AND ".join(conditions) if conditions else "1=1"
            
            # 获取总数
            cursor.execute(f"SELECT COUNT(*) as total FROM report_webhook_library WHERE {where_clause}", params)
            total = cursor.fetchone()['total']
            
            # 获取数据
            offset = (page - 1) * size
            cursor.execute(f"""
                SELECT * FROM report_webhook_library 
                WHERE {where_clause}
                ORDER BY created_at DESC
                LIMIT %s OFFSET %s
            """, params + [size, offset])
            
            rows = cursor.fetchall()
            
            # 格式化数据
            for row in rows:
                # 格式化时间字段（数据库存储的是北京时间，直接格式化为字符串）
                if row.get('created_at') and hasattr(row['created_at'], 'strftime'):
                    row['created_at'] = row['created_at'].strftime('%Y-%m-%d %H:%M:%S')
                if row.get('updated_at') and hasattr(row['updated_at'], 'strftime'):
                    row['updated_at'] = row['updated_at'].strftime('%Y-%m-%d %H:%M:%S')
                
                # 解析JSON字段
                if row.get('wecom_mention'):
                    row['wecom_mention'] = json.loads(row['wecom_mention']) if isinstance(row['wecom_mention'], str) else row['wecom_mention']
                if row.get('email_recipients'):
                    row['email_recipients'] = json.loads(row['email_recipients']) if isinstance(row['email_recipients'], str) else row['email_recipients']
                if row.get('email_cc'):
                    row['email_cc'] = json.loads(row['email_cc']) if isinstance(row['email_cc'], str) else row['email_cc']
                if row.get('extra_config'):
                    row['extra_config'] = json.loads(row['extra_config']) if isinstance(row['extra_config'], str) else row['extra_config']
            
            return jsonify({
                'success': True,
                'message': 'ok',
                'data': {
                    'rows': rows,
                    'total': total,
                    'page': page,
                    'size': size
                }
            })
        finally:
            cursor.close()
            conn.close()
            
    except Exception as e:
        # 检查是否为数据库连接错误
        is_conn_error, friendly_msg = is_database_connection_error(e)
        
        if is_conn_error:
            current_app.logger.error(f"获取webhook库失败（数据库连接错误）: {str(e)}", exc_info=True)
            return jsonify({
                'success': False,
                'message': friendly_msg,
                'data': None
            }), 500
        else:
            current_app.logger.error(f"获取webhook库失败: {str(e)}", exc_info=True)
            return jsonify({
                'success': False,
                'message': f'获取webhook库失败：{str(e)}',
                'data': None
            }), 500


@reports_bp.route('/webhook-library', methods=['POST'])
@log_api_call("reports.create_webhook")
def create_webhook():
    """创建webhook地址"""
    try:
        data = request.get_json()
        
        conn = get_db_connection()
        cursor = conn.cursor()
        
        try:
            # 将JSON字段序列化
            wecom_mention = json.dumps(data.get('wecom_mention')) if data.get('wecom_mention') else None
            email_recipients = json.dumps(data.get('email_recipients')) if data.get('email_recipients') else None
            email_cc = json.dumps(data.get('email_cc')) if data.get('email_cc') else None
            extra_config = json.dumps(data.get('extra_config')) if data.get('extra_config') else None
            
            cursor.execute("""
                INSERT INTO report_webhook_library 
                (name, channel_type, webhook_url, wecom_mention, email_recipients, email_cc, 
                 email_subject_template, email_body_template, description, extra_config, is_active)
                VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)
            """, (
                data['name'],
                data.get('channel_type', 'wecom'),
                data.get('webhook_url'),
                wecom_mention,
                email_recipients,
                email_cc,
                data.get('email_subject_template'),
                data.get('email_body_template'),
                data.get('description', ''),
                extra_config,
                data.get('is_active', 1)
            ))
            
            conn.commit()
            webhook_id = cursor.lastrowid
            
            return jsonify({
                'success': True,
                'message': '创建成功',
                'data': {'id': webhook_id}
            })
        finally:
            cursor.close()
            conn.close()
            
    except pymysql.err.IntegrityError as e:
        return jsonify({
            'success': False,
            'message': 'Webhook名称已存在',
            'data': None
        }), 400
    except Exception as e:
        current_app.logger.error(f"创建webhook失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'创建失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/webhook-library/<int:webhook_id>', methods=['GET'])
@log_api_call("reports.get_webhook_detail")
def get_webhook_detail(webhook_id):
    """获取单个webhook详情"""
    try:
        conn = get_db_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        
        try:
            cursor.execute("SELECT * FROM report_webhook_library WHERE id = %s", (webhook_id,))
            webhook = cursor.fetchone()
            
            if not webhook:
                return jsonify({
                    'success': False,
                    'message': 'Webhook不存在',
                    'data': None
                }), 404
            
            # 解析JSON字段
            if webhook.get('wecom_mention'):
                webhook['wecom_mention'] = json.loads(webhook['wecom_mention']) if isinstance(webhook['wecom_mention'], str) else webhook['wecom_mention']
            if webhook.get('email_recipients'):
                webhook['email_recipients'] = json.loads(webhook['email_recipients']) if isinstance(webhook['email_recipients'], str) else webhook['email_recipients']
            if webhook.get('email_cc'):
                webhook['email_cc'] = json.loads(webhook['email_cc']) if isinstance(webhook['email_cc'], str) else webhook['email_cc']
            if webhook.get('extra_config'):
                webhook['extra_config'] = json.loads(webhook['extra_config']) if isinstance(webhook['extra_config'], str) else webhook['extra_config']
            
            return jsonify({
                'success': True,
                'message': 'ok',
                'data': webhook
            })
        finally:
            cursor.close()
            conn.close()
            
    except Exception as e:
        current_app.logger.error(f"获取webhook详情失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'获取失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/webhook-library/<int:webhook_id>', methods=['PUT'])
@log_api_call("reports.update_webhook")
def update_webhook(webhook_id):
    """更新webhook地址"""
    try:
        data = request.get_json()
        
        conn = get_db_connection()
        cursor = conn.cursor()
        
        try:
            update_fields = []
            params = []
            
            for field in ['name', 'channel_type', 'webhook_url', 'email_subject_template', 
                         'email_body_template', 'description', 'is_active']:
                if field in data:
                    update_fields.append(f"{field} = %s")
                    params.append(data[field])
            
            # 处理JSON字段
            if 'wecom_mention' in data:
                update_fields.append("wecom_mention = %s")
                params.append(json.dumps(data['wecom_mention']) if data['wecom_mention'] else None)
            
            if 'email_recipients' in data:
                update_fields.append("email_recipients = %s")
                params.append(json.dumps(data['email_recipients']) if data['email_recipients'] else None)
            
            if 'email_cc' in data:
                update_fields.append("email_cc = %s")
                params.append(json.dumps(data['email_cc']) if data['email_cc'] else None)
            
            if 'extra_config' in data:
                update_fields.append("extra_config = %s")
                params.append(json.dumps(data['extra_config']) if data['extra_config'] else None)
            
            if not update_fields:
                return jsonify({
                    'success': False,
                    'message': '没有可更新的字段',
                    'data': None
                }), 400
            
            params.append(webhook_id)
            cursor.execute(f"""
                UPDATE report_webhook_library 
                SET {', '.join(update_fields)}
                WHERE id = %s
            """, params)
            
            conn.commit()
            
            if cursor.rowcount == 0:
                return jsonify({
                    'success': False,
                    'message': 'Webhook不存在',
                    'data': None
                }), 404
            
            return jsonify({
                'success': True,
                'message': '更新成功',
                'data': None
            })
        finally:
            cursor.close()
            conn.close()
            
    except Exception as e:
        current_app.logger.error(f"更新webhook失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'更新失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/webhook-library/<int:webhook_id>', methods=['DELETE'])
@log_api_call("reports.delete_webhook")
def delete_webhook(webhook_id):
    """删除webhook地址"""
    try:
        conn = get_db_connection()
        cursor = conn.cursor()
        
        try:
            cursor.execute("DELETE FROM report_webhook_library WHERE id = %s", (webhook_id,))
            conn.commit()
            
            if cursor.rowcount == 0:
                return jsonify({
                    'success': False,
                    'message': 'Webhook不存在',
                    'data': None
                }), 404
            
            return jsonify({
                'success': True,
                'message': '删除成功',
                'data': None
            })
        finally:
            cursor.close()
            conn.close()
            
    except Exception as e:
        current_app.logger.error(f"删除webhook失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'删除失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/webhook-library/<int:webhook_id>/test', methods=['POST'])
@log_api_call("reports.test_webhook")
def test_webhook(webhook_id):
    """测试webhook连接"""
    try:
        conn = get_db_connection()
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        
        try:
            cursor.execute("SELECT * FROM report_webhook_library WHERE id = %s", (webhook_id,))
            webhook = cursor.fetchone()
            
            if not webhook:
                return jsonify({
                    'success': False,
                    'message': 'Webhook不存在',
                    'data': None
                }), 404
            
            # 根据渠道类型发送测试消息
            if webhook['channel_type'] == 'wecom':
                import requests
                test_message = {
                    "msgtype": "text",
                    "text": {
                        "content": "这是一条来自Flink监控系统的测试消息"
                    }
                }
                
                response = requests.post(webhook['webhook_url'], json=test_message, timeout=5)
                
                if response.status_code == 200:
                    return jsonify({
                        'success': True,
                        'message': '测试成功',
                        'data': {'response': response.json()}
                    })
                else:
                    return jsonify({
                        'success': False,
                        'message': f'测试失败: HTTP {response.status_code}',
                        'data': {'response': response.text}
                    }), 400
            
            elif webhook['channel_type'] == 'email':
                # Email测试逻辑待实现
                return jsonify({
                    'success': False,
                    'message': '邮件渠道测试功能待实现',
                    'data': None
                }), 501
            
            else:
                return jsonify({
                    'success': False,
                    'message': f'不支持的渠道类型: {webhook["channel_type"]}',
                    'data': None
                }), 400
                
        finally:
            cursor.close()
            conn.close()
            
    except Exception as e:
        current_app.logger.error(f"测试webhook失败: {str(e)}", exc_info=True)
        return jsonify({
            'success': False,
            'message': f'测试失败：{str(e)}',
            'data': None
        }), 500


# ==================== 统计信息 ====================

@reports_bp.route('/stats', methods=['GET'])
@log_api_call("reports.get_stats")
def get_stats():
    """获取报表统计信息"""
    current_app.logger.info("Getting reports stats")
    
    conn = get_db_connection()
    try:
        with conn.cursor() as cur:
            # 总报表数
            cur.execute("SELECT COUNT(*) as total FROM report_runs")
            total_reports = cur.fetchone()['total']
            
            # 今日生成数
            today = datetime.now().strftime('%Y-%m-%d')
            cur.execute("""
                SELECT COUNT(*) as total 
                FROM report_runs 
                WHERE DATE(created_at) = %s
            """, (today,))
            today_generated = cur.fetchone()['total']
            
            # 历史报表数（与总报表数相同）
            history_reports = total_reports
            
            # 报表配置数（激活的配置）
            cur.execute("""
                SELECT COUNT(*) as total 
                FROM report_configs 
                WHERE is_active = 1
            """)
            total_configs = cur.fetchone()['total']
            
            return {
                "success": True,
                "message": "ok",
                "data": {
                    "total_reports": total_reports,
                    "today_generated": today_generated,
                    "history_reports": history_reports,
                    "total_configs": total_configs
                }
            }
    finally:
        conn.close()


# ==================== Cron 表达式校验与预览 ====================

@reports_bp.route('/cron/validate', methods=['GET'])
@log_api_call("reports.cron_validate")
def validate_cron():
    """
    校验 Cron 表达式并返回未来 n 次运行时间与可读描述
    支持 5 位（分 时 日 月 周）与 6 位（秒 分 时 日 月 周）
    """
    try:
        expr = request.args.get('expr', '', type=str).strip()
        n = request.args.get('n', 3, type=int)
        if not expr:
            return jsonify({'success': True, 'message': 'empty', 'data': {'valid': False, 'message': '表达式为空', 'next_runs': [], 'readable': ''}})
        
        parts = expr.split()
        if len(parts) not in (5, 6):
            return jsonify({'success': True, 'message': 'invalid', 'data': {'valid': False, 'message': f'需为5或6段表达式，当前{len(parts)}段', 'next_runs': [], 'readable': ''}})
        
        # 解析为 CronTrigger
        try:
            if len(parts) == 6:
                second, minute, hour, day, month, dow = parts
                trigger = CronTrigger(second=second, minute=minute, hour=hour, day=day, month=month, day_of_week=dow, timezone='Asia/Shanghai')
            else:
                minute, hour, day, month, dow = parts
                trigger = CronTrigger(minute=minute, hour=hour, day=day, month=month, day_of_week=dow, timezone='Asia/Shanghai')
        except Exception as e:
            return jsonify({'success': True, 'message': 'invalid', 'data': {'valid': False, 'message': f'解析失败：{str(e)}', 'next_runs': [], 'readable': ''}})
        
        # 计算未来 n 次时间
        from datetime import datetime
        tz_chn = timezone(timedelta(hours=8))
        now = datetime.now(tz_chn)
        next_runs = []
        next_time = trigger.get_next_fire_time(None, now)
        count = 0
        while next_time and count < max(0, n):
            next_runs.append(next_time.strftime('%Y-%m-%d %H:%M:%S'))
            # 使用之前时间作为上次触发，获取下一次
            next_time = trigger.get_next_fire_time(previous_fire_time=next_time, now=next_time)
            count += 1
        
        # 简单可读化
        def humanize(parts_list):
            p = parts_list
            if len(p) == 6:
                s, m, h, d, mo, w = p
            else:
                s, m, h, d, mo, w = '0', p[0], p[1], p[2], p[3], p[4]
            def is_any(v): return v in ('*', '*/1')
            if is_any(mo) and is_any(d) and is_any(w) and not is_any(h) and not is_any(m):
                return f'每天{h}点{m}分{("" if s=="0" else s+"秒")}运行'
            if is_any(mo) and not is_any(w) and is_any(d):
                return f'每周{w}的 {h}点{m}分{("" if s=="0" else s+"秒")}运行'
            if is_any(w) and not is_any(d) and is_any(mo):
                return f'每月{d}日 {h}点{m}分{("" if s=="0" else s+"秒")}运行'
            return f'表达式：{expr}'
        
        readable = humanize(parts)
        
        return jsonify({
            'success': True,
            'message': 'ok',
            'data': {
                'valid': True,
                'message': '',
                'next_runs': next_runs,
                'readable': readable
            }
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'Cron校验失败：{str(e)}',
            'data': None
        }), 500


# ==================== 调度器状态（报表视图内便捷查询） ====================
@reports_bp.route('/scheduler/status', methods=['GET'])
@log_api_call("reports.scheduler_status")
def reports_scheduler_status():
    """
    返回报表调度器运行状态与任务概要
    """
    try:
        from app.utils.scheduler import get_scheduler
        from app.services.report_scheduler import get_report_scheduler
        
        scheduler = get_scheduler()
        report_scheduler = get_report_scheduler()
        
        # 初始化默认状态
        jobs_info = []
        scheduler_running = False
        
        if scheduler:
            try:
                scheduler_running = scheduler.running if hasattr(scheduler, 'running') else False
            except Exception:
                scheduler_running = False
            
            # 获取所有报表任务（需要 report_scheduler 存在且调度器已初始化）
            if report_scheduler and hasattr(report_scheduler, 'scheduler') and report_scheduler.scheduler:
                try:
                    # 确保调度器实例有效
                    if hasattr(report_scheduler.scheduler, 'get_jobs'):
                        report_jobs = report_scheduler._get_report_jobs()
                        for job in report_jobs:
                            try:
                                # 安全地获取 next_run_time（某些情况下可能不存在）
                                next_run_time = getattr(job, 'next_run_time', None)
                                next_run = next_run_time.strftime('%Y-%m-%d %H:%M:%S') if next_run_time else None
                                jobs_info.append({
                                    'id': job.id,
                                    'name': job.name if hasattr(job, 'name') else job.id,
                                    'next_run': next_run
                                })
                            except Exception:
                                # 单个任务信息获取失败，跳过
                                continue
                except Exception:
                    # 获取任务列表失败，但不影响整体状态返回
                    pass
        
        status = {
            'scheduler_running': scheduler_running,
            'jobs': jobs_info,
            'total_jobs': len(jobs_info),
            'message': 'ok'
        }
        
        return jsonify({
            'success': True,
            'message': 'ok',
            'data': status
        })
    except Exception as e:
        import traceback
        error_msg = f'获取调度器状态失败：{str(e)}'
        # 记录详细错误信息到日志
        from app.utils.logger import FlinkLogger
        logger = FlinkLogger.get_logger(__name__)
        logger.error(f"{error_msg}\n{traceback.format_exc()}")
        
        return jsonify({
            'success': False,
            'message': error_msg,
            'data': None
        }), 500


@reports_bp.route('/scheduler/start', methods=['POST'])
@log_api_call("reports.scheduler_start")
def reports_scheduler_start():
    """启动报表调度器并同步任务"""
    try:
        from app.utils.scheduler import get_scheduler, start_scheduler
        from app.services.report_scheduler import get_report_scheduler, set_report_scheduler, init_report_scheduler
        
        # 启动全局调度器（如果已关闭会重新创建）
        start_scheduler()
        
        # 获取最新的调度器实例
        scheduler = get_scheduler()
        
        # 确保 ReportScheduler 使用最新的调度器实例
        report_scheduler = get_report_scheduler()
        if report_scheduler:
            # 更新调度器引用
            report_scheduler.scheduler = scheduler
        else:
            # 如果不存在，重新初始化
            report_scheduler = init_report_scheduler(scheduler)
            set_report_scheduler(report_scheduler)
        
        # 同步报表任务
        report_scheduler.sync_all_configs()
        
        # 获取状态
        report_jobs = report_scheduler._get_report_jobs() if report_scheduler else []
        
        status = {
            'scheduler_running': scheduler.running if scheduler else False,
            'total_jobs': len(report_jobs),
            'message': '报表调度器已启动'
        }
        
        return jsonify({
            'success': True,
            'message': '报表调度器已启动',
            'data': status
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'启动失败：{str(e)}',
            'data': None
        }), 500


@reports_bp.route('/scheduler/stop', methods=['POST'])
@log_api_call("reports.scheduler_stop")
def reports_scheduler_stop():
    """停止报表调度器"""
    try:
        from app.utils.scheduler import get_scheduler, shutdown_scheduler
        
        # 停止全局调度器
        shutdown_scheduler(wait=False)
        
        scheduler = get_scheduler()
        status = {
            'scheduler_running': scheduler.running if scheduler else False,
            'message': '报表调度器已停止'
        }
        
        return jsonify({
            'success': True,
            'message': '报表调度器已停止',
            'data': status
        })
    except Exception as e:
        return jsonify({
            'success': False,
            'message': f'停止失败：{str(e)}',
            'data': None
        }), 500
